跳到主要内容

SpringSecurity 工具类 BCryptPasswordEncoder 是如何加密解密的

SpringSecurity 中 PasswordEncoder 的加密是采用 SHA-256 + 随机盐 + 密钥进行加密,那解密时如何解密的?

如下实例:

public class BCryptPasswordEncoderTest {
public static void main(String[] args) {
String pass = "admin";
BCryptPasswordEncoder bcryptPasswordEncoder = new BCryptPasswordEncoder();
String hashPass = bcryptPasswordEncoder.encode(pass);
System.out.println(hashPass);

boolean f = bcryptPasswordEncoder.matches("admin", hashPass);
System.out.println(f);

}
}

可以看到,每次输出的 hashPass 都不一样,但是最终的 f 都为 true,即匹配成功。

查看代码,可以看到,其实每次的随机盐,都保存在 hashPass 中。

在进行 matchs 进行比较时,都会调用 BCrypt 的 String hashpw(String password, String salt) 方法。两个参数即 admin 和 hashPass

//******BCrypt.java******salt即取出要比较的DB中的密码*******
real_salt = salt.substring(off + 3, off + 25);
try {
// ***************************************************
passwordb = (password + (minor >= 'a' ? "\000" : "")).getBytes("UTF-8");
}
catch (UnsupportedEncodingException uee) {}
saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);
B = new BCrypt();
hashed = B.crypt_raw(passwordb, saltb, rounds);

假定一次 hashPass 为:

$2a$10$AxafsyVqK51p.s9WAEYWYeIY9TKEoG83LTEOSB3KUkoLtGsBKhCwe

随机盐即为 AxafsyVqK51p.s9WAEYWYe ,而这个随机盐,会在比较的时候,重新被取出。

补充:Spring Security 的 PasswordEncoder 可以选多种 Hash 算法,然后可以配置是否把 Hash 结果用 Base64 编码(或者用Hex十六进制表示,默认),然后得到的结果存库(假设用的 DB 存储);

然后密码匹配阶段并没有进行解密(因为是 Hash,是不可逆的,也即解不开),而是根据配置,先进行解码(注意是解码),得到 hash 值;然后使用相同的算法把现在用户输入的密码进行 hash,然后看现在的 hash 值是否与刚刚解码出来(从库中用户表读取的) hash 值相等,如果等说明用户密码正确。

这正是为什么处理密码要用 hash 算法,而不用加密,因为这样处理即使库泄漏,黑客也很难(加盐可以说几乎不可能,MD5 除外)破解密码(破解密码只能用彩虹表用相同的 hash 算法 hash 然后匹配,一个一个试)。